



























#include "hook.h"

#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/types.h>


namespace learnco{

// 只在协程中调用开启HOOK
static thread_local bool t_hook_enable = false;

//使用宏定义，声明函数指针
//sleep_f : 
#define HOOK_FUN(FUN) \
    FUN(sleep)   \
    FUN(socket)  \
    FUN(connect) \
    FUN(send)    \
    FUN(recv)    

struct _HOOK_INIT{
    _HOOK_INIT(){
//FUN(sleep) ：sleep_f = (sleep_fun) dlsym(RTLD_NEXT, "sleep");
#define FUN(name) name ## _f  = (name ## _fun) dlsym(RTLD_NEXT, #name);
    HOOK_FUN(FUN);
//undef 取消宏定义
#undef  FUN 
    }
};

//在main函数运行前声明函数指针
static _HOOK_INIT hook_var_initer;

bool is_hook_enable(){
    return t_hook_enable;
}

void set_hook_enable(bool flag){
    t_hook_enable = flag;
}
    
}

//用于判断定时任务是否继续执行
struct Timercond {
    bool cancel = false;
};

//通用IO处理
//&&(模板或auto下，万能引用)
template<typename IOFun, typename... Args>
static ssize_t asynio_handle(int sockfd, IOFun io_fun, learnco::IOmanger::Event event, Args&&... args){
    if(!learnco::t_hook_enable){
        return io_fun(sockfd, std::forward<Args>(args)...);
    }

    const int TIMEOUT = 5000;

    //非阻塞 调用, 防止被中断
    ssize_t recv_count;
    do{
        recv_count = io_fun(sockfd, std::forward<Args>(args)...);
    }while(recv_count ==-1 && errno==EINTR);


    //非阻塞读call sucess, 数据未准备好
    if(recv_count == -1 && errno == EAGAIN){

        learnco::Fiber::ptr fiber = learnco::Fiber::GetThis();
        learnco::IOmanger* iom = learnco::IOmanger::GetThis();

        std::shared_ptr<Timercond> time_cond (new Timercond);
        std::weak_ptr<Timercond> w_timecond (time_cond);
        iom->addTimer(TIMEOUT, [w_timecond, iom, event, sockfd](){
            auto t = w_timecond.lock();
            if(!t || t->cancel){
                return ;
            }
            t->cancel = true;
            iom->delEvent(sockfd, event, true);
        }, false);

        //当event事件被触发后，跳转到该协程上下文
        assert(iom->addEvent(sockfd, event, nullptr));

        //让出cpu执行权，等待被 唤醒
        learnco::Fiber::YieldToHold();

        //...会阻塞在这里
        if(time_cond->cancel){
            //超时
            errno = ETIMEDOUT;
            return -1;
        }
        else{
            //IO事件到达 再次调用io读取/写入数据
            time_cond->cancel = true;
            return io_fun(sockfd, std::forward<Args>(args)...);
        }
    }

    return recv_count;
    
}

//----------------------------------------------------------------------
extern "C" 
{
//先声明，_HOOK_INIT会完成赋值
//FUN(sleep) : sleep_fun sleep_f = nullptr;
#define FUN(name) name ## _fun name ## _f  = nullptr;
    HOOK_FUN(FUN);
#undef FUN

unsigned int sleep(unsigned int seconds){
    if(!learnco::t_hook_enable){
        return sleep_f(seconds);
    }

    learnco::Fiber::ptr fiber = learnco::Fiber::GetThis();
    learnco::IOmanger* iom = learnco::IOmanger::GetThis();

    iom->addTimer(seconds*1000, [=](){
        iom->scheduler(fiber);
    },false);

    learnco::Fiber::YieldToHold();
    //定时任务结束后，程序会跳转到这里，继续往下执行
    return 0;
}

int socket(int domain, int type, int protocol){
    if(!learnco::t_hook_enable){
        return socket_f(domain, type, protocol);
    }

    int sd =  socket_f(domain, type, protocol);
    if(sd < 0){
        return -1;
    }

    //设置sd非阻塞
    int flags = fcntl(sd, F_GETFL, 0);
    if(!(flags & O_NONBLOCK)) {
        fcntl(sd, F_SETFL, flags | O_NONBLOCK);
    }

    return sd;
}

//内核默认connect 超时时间75s
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen){
    if(!learnco::t_hook_enable){
        return connect_f(sockfd, addr, addrlen);
    }

    //默认connect超时时间
    const uint64_t TIMEOUT = 1000;

    //执行非阻塞连接
    //EINPROGRESS EAGAIN : The socket is nonblocking and the connection cannot be completed immediately
    int n = connect_f(sockfd, addr, addrlen);
    if(n==0){
        // 连接成功
        return 0;
    }else if(errno != EINPROGRESS &&  errno!=EAGAIN){
        return n;
    }   


    learnco::Fiber::ptr fiber = learnco::Fiber::GetThis();
    learnco::IOmanger* iom = learnco::IOmanger::GetThis();
    //添加超时任务:timeout唤醒该协程
    //若协程timeout之前被触发(使用timer_cond判断)，该超时任务无效，不会被执行
    //time_cond用于检测该fiber是否执行完毕，若执行完毕，则不执行超时任务
    //time_cond，离开该函数会自动释放
    std::shared_ptr<Timercond> time_cond (new Timercond);
    std::weak_ptr<Timercond> w_timecond (time_cond);
    iom->addTimer(TIMEOUT, [w_timecond, iom, fiber, sockfd](){
        auto t = w_timecond.lock();
        if(!t || t->cancel){
            return ;
        }
        t->cancel = true;
        iom->delEvent(sockfd, learnco::IOmanger::WRITE, true);
    }, false);

    //添加连接成功事件
    //当WRITE事件被触发后，跳转到该协程上下文
    assert(iom->addEvent(sockfd, learnco::IOmanger::WRITE, nullptr));
    
    //让出执行权，等待被超时/写事件 唤醒
    learnco::Fiber::YieldToHold();

    //...会阻塞在这里
    if(!time_cond->cancel){
        //WRAITE事件
        time_cond->cancel = true;
        return 0;
    }
    else{
        //超时
        errno = ETIMEDOUT;
        return -1;
    }
}


ssize_t send(int sockfd, const void *buf, size_t len, int flags){
    return asynio_handle(sockfd, send_f, learnco::IOmanger::WRITE, buf, len, flags);
}

ssize_t recv(int sockfd, void *buf, size_t len, int flags){
    return asynio_handle(sockfd, recv_f, learnco::IOmanger::READ, buf, len, flags);
}




}